/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package bolao.service;


import bolao.exception.NonExistentObject;
import bolao.service.interceptors.CrudServiceInterceptor;
import bolao.util.ReflectionUtil;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.ejb.Stateless;
import javax.interceptor.Interceptors;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author 002824992500
 */
@Stateless
@Interceptors(CrudServiceInterceptor.class)
public class CRUDService {

    @PersistenceContext
    private EntityManager em;
    private ReflectionUtil reflectionUtil;

    @PostConstruct
    private void init() {
        this.reflectionUtil = new ReflectionUtil();
    }

    public <T> T save(T t) {
        verificaSeFoiExcluidoPorOutraSessao(t);
        t = em.merge(t);
        em.flush();
        return t;
    }

    public void saveArray(Object[] objArray) {
        for (Object obj : objArray) {
            save(obj);
        }
    }

    public void verificaSeFoiExcluidoPorOutraSessao(Object obj) {
        if (foiExcluidoPorOutraSessao(obj)) {
            throw new NonExistentObject();
        }
    }

    public <T> T find(Class type, Object id) {
        return (T) em.find(type, id);
    }

    public <T> List<T> findAll(Class<T> clazz) {
        return em.createQuery("select object(o) from " + clazz.getSimpleName() + " as o").getResultList();
    }

    public int executeUpdate(String strQuery, Object... parametros) {
        Query q = configureQuery(strQuery, parametros);
        return q.executeUpdate();
    }

    public <T> List<T> executeQuery(String EJBQLQuery, Object... parametros) {
        Query query = configureQuery(EJBQLQuery, parametros);
        return query.getResultList();
    }

    public <T> List<T> executeQuery(String EJBQLQuery, Map queryHints, Object... parametros) {
        Query query;
        if (parametros != null && parametros.length > 0 && parametros[0] != null && parametros[0] instanceof List) {
            query = configureQuery(EJBQLQuery, ((List) parametros[0]).toArray());
        } else {
            query = configureQuery(EJBQLQuery, parametros);
        }

        for (Object hintName : queryHints.keySet()) {
            String hintValue = (String) queryHints.get(hintName);
            query.setHint((String) hintName, hintValue);
        }
        return query.getResultList();
    }

    private Query configureQuery(String EJBQLQuery, Object[] parametros) {
        Query query = em.createQuery(EJBQLQuery);
        setParametros(parametros, query);
        return query;
    }

    private Query configureNativeQuery(String sqlQuery, Object[] parametros) {
        Query query = em.createNativeQuery(sqlQuery);
        setParametros(parametros, query);
        return query;
    }

    private void setParametros(Object[] parametros, Query query) {
        int i = 0;
        for (Object param : parametros) {
            query.setParameter(++i, param);
        }
    }

    private static final Logger log = LoggerFactory.getLogger(CRUDService.class);

    public Object executeQuerySingleResult(String EJBQLQuery, Object... parametros) {
        Query query = configureQuery(EJBQLQuery, parametros);
        Object obj;
        try {
            obj = query.getSingleResult();
        } catch (NoResultException e) {
            obj = null;
        }
        return obj;
    }

    public <T> List<T> executeNativeQuery(String sqlQuery, Class<T> clazz) {
        List<T> list = (List<T>) em.createNativeQuery(sqlQuery, clazz).getResultList();
        return list;
    }

    public void executeNativeUpdate(String sqlQuery, Object... parametros) {
        Query query = configureNativeQuery(sqlQuery, parametros);
        query.executeUpdate();
        em.flush();
    }

    public void executeStoredProcedure(String fullyQualifiedProcedureName, Object... parametros) {
        executeNativeUpdate("BEGIN " + fullyQualifiedProcedureName + "; END;", parametros);
    }

    public void delete(Object t) {

        verificaSeFoiExcluidoPorOutraSessao(t);

        em.remove(em.merge(t));
        em.flush();
    }

    public void refresh(Class type, Object id) {
        Object obj = this.find(type, id);
        em.refresh(obj);
        em.flush();
    }

    public void removeList(List lista) {
        for (Object obj : lista) {
            delete(obj);
        }
    }

    /**
     * Verifica se o objeto passado como argumento já não foi excluído do
     * banco de dados por outra sessão de usuário concorrente.
     * ATENÇÃO: Esse método pressupõe que o objeto passado como argumento seja 
     * de uma classe persistente do JPA (anotado com @Entity e @Id ou @EmbeddedId)
     *
     * @param t Objeto a ser pesquisado no mecanismo de persistência.
     * @return TRUE caso o objeto passado como argumento tenha sido excluído do banco de dados 
     * por outra sessão de usuário concorrente e retorna FALSE, caso o objeto
     * ainda exista no banco de dados.
     */
    public boolean foiExcluidoPorOutraSessao(Object t) {
        Object pkValue = reflectionUtil.getPKValue(t);
        if (pkValue == null) {
            return false;
        }
        Object foundObject = this.find(t.getClass(), pkValue);
        if (foundObject == null) {
            return true;
        } else {
            return false;
        }
    }


}
